package com.fa4j.system.login.service.impl;

import com.alibaba.fastjson2.JSON;
import com.fa4j.common.web.model.ClientInfo;
import com.fa4j.system.account.model.Account;
import com.fa4j.system.account.model.AccountType;
import com.fa4j.system.account.repository.AccountRepository;
import com.fa4j.system.file.service.FileUrlFillService;
import com.fa4j.system.login.model.LoginAppProps;
import com.fa4j.system.login.model.LoginConstants;
import com.fa4j.system.login.model.LoginSession;
import com.fa4j.system.login.service.LoginPropsService;
import com.fa4j.system.login.service.LoginSessionService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.util.ObjectUtils;

import java.time.Duration;
import java.time.LocalDateTime;
import java.util.Optional;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

@Slf4j
@Service
@RequiredArgsConstructor
public class LoginSessionServiceImpl implements LoginSessionService {

    private final static String SESSION_TOKEN_KEY = "session:t:%s";
    private final static String SESSION_ACCOUNT_KEY = "session:a:%s";

    private final LoginPropsService loginPropsService;
    private final AccountRepository accountRepository;
    private final FileUrlFillService fileUrlFillService;

    private final RedisTemplate<String, String> redisTemplate;


    /**
     * 创建 token
     *
     * @param account    权限账号信息
     * @param clientInfo 客户端信息
     * @return 登录信息
     */
    @Override
    public LoginSession createSession(Account account, ClientInfo clientInfo) {
        LoginAppProps prop = loginPropsService.getAppTypeItemProp(account.getAppType());

        String token = UUID.randomUUID().toString().replace("-", "");
        LoginSession session = new LoginSession();
        session.setAppType(account.getAppType());
        session.setAccountId(account.getId());
        session.setAccessToken(token);
        session.setCreateTime(LocalDateTime.now());

        session.setExpireTime(LocalDateTime.now().plusMinutes(prop.getExpireMinutes()));
        fileUrlFillService.fill(account, Account::getAvatarId, Account::setAvatarUrl);
        if (account.getType() == AccountType.ADMIN) {
            account.setRoleCodes(Set.of(LoginConstants.ROLE_ADMIN));
        }
        session.setAccount(account);
        session.setClientInfo(clientInfo);

        String tokenKey = SESSION_TOKEN_KEY.formatted(token);
        redisTemplate.opsForValue().set(tokenKey, JSON.toJSONString(session), prop.getExpireMinutes(), TimeUnit.MINUTES);

        String accountKey = SESSION_ACCOUNT_KEY.formatted(account.getId());
        redisTemplate.opsForZSet().add(accountKey, token, System.currentTimeMillis());
        redisTemplate.expire(accountKey, prop.getExpireMinutes(), TimeUnit.MINUTES);
        return session;
    }

    /**
     * 删除 token
     *
     * @param token 令牌
     */
    @Override
    public void remove(String token) {
        String tokenKey = SESSION_TOKEN_KEY.formatted(token);
        String json = redisTemplate.opsForValue().get(tokenKey);
        if (ObjectUtils.isEmpty(json)) {
            return;
        }
        redisTemplate.delete(tokenKey);
        LoginSession session = JSON.parseObject(json, LoginSession.class);
        String accountKey = SESSION_ACCOUNT_KEY.formatted(session.getAccount().getId());
        redisTemplate.opsForZSet().remove(accountKey, token);
    }

    /**
     * 删除账号 token
     *
     * @param accountId 账号 ID
     */
    @Override
    public void removeAccountSession(Long accountId) {
        String accountKey = SESSION_ACCOUNT_KEY.formatted(accountId);
        Set<String> tokens = redisTemplate.opsForZSet().range(accountKey, 0, -1);
        if (tokens == null || tokens.isEmpty()) {
            return;
        }
        for (String token : tokens) {
            String tokenKey = SESSION_TOKEN_KEY.formatted(token);
            redisTemplate.delete(tokenKey);
        }
        redisTemplate.delete(accountKey);
    }

    /**
     * 获取当前登录信息
     */
    public Optional<LoginSession> get(String token) {
        String tokenKey = SESSION_TOKEN_KEY.formatted(token);
        String json = redisTemplate.opsForValue().get(tokenKey);
        if (ObjectUtils.isEmpty(json)) {
            return Optional.empty();
        }
        LoginSession session = JSON.parseObject(json, LoginSession.class);
        LoginAppProps prop = loginPropsService.getAppTypeItemProp(session.getAppType());

        // 如果令牌剩余有效期不足一半需要刷新有效期
        Duration duration = Duration.between(LocalDateTime.now(), session.getExpireTime());
        if (duration.getSeconds() < (prop.getExpireMinutes() * 60 / 2)) {
            session.setExpireTime(LocalDateTime.now().plusMinutes(prop.getExpireMinutes()));
            log.info("刷新 session: {}", session);
            redisTemplate.opsForValue().set(tokenKey, JSON.toJSONString(session), prop.getExpireMinutes(), TimeUnit.MINUTES);
            String accountKey = SESSION_ACCOUNT_KEY.formatted(session.getAccount().getId());
            redisTemplate.expire(accountKey, prop.getExpireMinutes(), TimeUnit.MINUTES);
        }
        return Optional.of(session);
    }


    /**
     * 刷新session 信息
     *
     * @param accountId 账号 ID
     */
    @Override
    public void refreshSession(Long accountId) {
        String accountKey = SESSION_ACCOUNT_KEY.formatted(accountId);
        Set<String> tokens = redisTemplate.opsForZSet().range(accountKey, 0, -1);
        if (tokens == null || tokens.isEmpty()) {
            return;
        }
        Account account = accountRepository.findById(accountId).orElse(null);
        if (account != null) {
            fileUrlFillService.fill(account, Account::getAvatarId, Account::setAvatarUrl);
            if (account.getType() == AccountType.ADMIN) {
                account.setRoleCodes(Set.of(LoginConstants.ROLE_ADMIN));
            }
            for (String token : tokens) {
                String tokenKey = SESSION_TOKEN_KEY.formatted(token);
                String json = redisTemplate.opsForValue().get(tokenKey);
                if (!ObjectUtils.isEmpty(json)) {
                    LoginSession session = JSON.parseObject(json, LoginSession.class);
                    session.setAccount(account);
                    redisTemplate.opsForValue().set(tokenKey, JSON.toJSONString(session));
                    log.info("刷新session, accountId:{}", accountId);
                }
            }
        }
    }
}
